﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.ServiceLocation;
using Pap.Data;
using Pap.Data.Abstraction.Utils;

namespace Pap.Services.LookUps
{
    /// <summary>
    /// 
    /// </summary>
    public class LookUpRepository : ILookUpRepository
    {
        /// <summary>
        /// 
        /// </summary>
        IDatabase _Database;

        /// <summary>
        /// Initializes a new instance of the <see cref="LookUpRepository"/> class.
        /// </summary>
        /// <param name="database">The database.</param>
        public LookUpRepository(IDatabase database)
        {
            _Database = database;
        }

        /// <summary>
        /// Gets all.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <returns></returns>
        public IEnumerable<ILookUpEntity> GetAll(string tableName)
        {
            ITable currentTable;
            GetTableInformation(tableName, out currentTable);
            return currentTable.OfType<ILookUpEntity>();
        }

        /// <summary>
        /// Deletes all.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="entities">The entities.</param>
        public void DeleteAll(string tableName, IEnumerable<Guid> entities)
        {
            ITable currentTable = null;
            GetTableInformation(tableName, out currentTable);
            if (currentTable != null)
            {
                IQueryable<ILookUpEntity> queriable = currentTable.OfType<ILookUpEntity>();
                currentTable.DeleteAllOnSubmit(queriable.Where(item => entities.Contains(item.ID)));
            }
            else throw new Exception("Table not found");
        }

        /// <summary>
        /// Inserts the specified table name.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="name">The name.</param>
        public void Insert(string tableName, string name)
        {
            Type currentType;
            ITable currentTable;
            GetTableInformation(tableName, out currentType, out currentTable);

            if (currentTable != null)
            {
                ILookUpEntity entity = ServiceLocator.Current.GetInstance(currentType) as ILookUpEntity;
                entity.ID = Guid.NewGuid();
                entity.Name = name;
                currentTable.InsertOnSubmit(entity);
            }
            else throw new Exception("Table not found");
        }

        /// <summary>
        /// Inserts the specified table name.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="name">The name.</param>
        /// <param name="fkName">Name of the fk.</param>
        /// <param name="parentId">The parent id.</param>
        public void Insert(string tableName, string name, string fkName, Guid parentId)
        {
            Type currentType;
            ITable currentTable;
            GetTableInformation(tableName, out currentType, out currentTable);

            if (currentTable != null)
            {
                ILookUpEntity entity = ServiceLocator.Current.GetInstance(currentType) as ILookUpEntity;
                entity.ID = Guid.NewGuid();
                entity.Name = name;
                GetProperty(entity, fkName).SetValue(entity, parentId, null);
                currentTable.InsertOnSubmit(entity);
            }
            else throw new Exception("Table not found");
        }

        /// <summary>
        /// Inserts the specified table name.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="name">The name.</param>
        /// <param name="extraParams">The extra params.</param>
        public void Insert(string tableName, string name, Dictionary<string, string> extraParams)
        {
            Type currentType;
            ITable currentTable;
            GetTableInformation(tableName, out currentType, out currentTable);

            if (currentTable != null)
            {
                ILookUpEntity entity = ServiceLocator.Current.GetInstance(currentType) as ILookUpEntity;
                entity.ID = Guid.NewGuid();
                entity.Name = name;
                foreach (var item in extraParams)
                {
                    entity.GetType().GetProperty(item.Key).SetValue(entity, item.Value.ToGuid(), null);
                }
                currentTable.InsertOnSubmit(entity);
            }
            else throw new Exception("Table not found");
        }

        /// <summary>
        /// Updates the specified table name.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="id">The id.</param>
        /// <param name="name">The name.</param>
        public void Update(string tableName, Guid id, string name)
        {
            ITable currentTable;
            GetTableInformation(tableName, out currentTable);
            if (currentTable != null)
            {
                IQueryable<ILookUpEntity> queriable = currentTable.OfType<ILookUpEntity>();
                queriable.Single(entity => entity.ID == id).Name = name;
            }
            else throw new Exception("Table not found");
        }

        /// <summary>
        /// Updates the specified table name.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="id">The id.</param>
        /// <param name="name">The name.</param>
        /// <param name="fkName">Name of the fk.</param>
        /// <param name="parentId">The parent id.</param>
        public void Update(string tableName, Guid id, string name, string fkName, Guid parentId)
        {
            ITable currentTable;
            GetTableInformation(tableName, out currentTable);
            if (currentTable != null)
            {
                IQueryable<ILookUpEntity> queriable = currentTable.OfType<ILookUpEntity>();
                ILookUpEntity entity = queriable.Single(e => e.ID == id);
                entity.Name = name;
                GetProperty(entity, fkName).SetValue(entity, parentId, null);
            }
            else throw new Exception("Table not found");
        }

        /// <summary>
        /// Gets the type of the previous trials close.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ILookUpEntity> GetPreviousTrialsCloseType()
        {
            return _Database.LoanHistoryCloseType.Where(t =>
                t.ID == new Guid("0407fd60-846f-a72e-42aa-029c9f70a6be"));
        }

        /// <summary>
        /// Gets the table information.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="table">The table.</param>
        private void GetTableInformation(string tableName, out ITable table)
        {
            foreach (var property in _Database.GetType().GetProperties())
            {
                if (property.PropertyType.IsGenericType)
                {
                    if (typeof(ITable).IsAssignableFrom(property.PropertyType))
                    {
                        Type type = property.PropertyType.GetGenericArguments()[0];
                        if (type.Name.ToLower() == tableName.ToLower())
                        {
                            if (typeof(ILookUpEntity).IsAssignableFrom(type))
                            {
                                table = property.GetValue(_Database, null) as ITable;
                                return;
                            }
                        }
                    }
                }
            }
            table = null;
        }

        /// <summary>
        /// Gets the table information.
        /// </summary>
        /// <param name="tableName">Name of the table.</param>
        /// <param name="entityType">Type of the entity.</param>
        /// <param name="table">The table.</param>
        private void GetTableInformation(string tableName, out Type entityType, out ITable table)
        {
            foreach (var property in _Database.GetType().GetProperties())
            {
                if (property.PropertyType.IsGenericType)
                {
                    if (typeof(ITable).IsAssignableFrom(property.PropertyType))
                    {
                        Type type = property.PropertyType.GetGenericArguments()[0];
                        if (type.Name.ToLower() == tableName.ToLower())
                        {
                            if (typeof(ILookUpEntity).IsAssignableFrom(type))
                            {
                                entityType = type;
                                table = property.GetValue(_Database, null) as ITable;
                                return;
                            }
                        }
                    }
                }
            }

            entityType = null;
            table = null;
        }

        /// <summary>
        /// Gets the property.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="propName">Name of the prop.</param>
        /// <returns></returns>
        public PropertyInfo GetProperty(ILookUpEntity entity, string propName)
        {
            propName = propName.ToLower();
            foreach (var item in entity.GetType().GetProperties())
            {
                if (item.Name.ToLower() == propName)
                    return item;
            }
            return null;
        }
    }
}
